package com.cheng.gmall.item.aspect;

import com.cheng.gmall.item.service.CacheService;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ParserContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;

/**
 * @author Cheng-02
 * @version V1.0
 * @Package com.cheng.gmall.item.aspect
 * @date 2024/6/1 下午1:12
 */

@Component //加入容器中
@Aspect //声明这是一个切面
@Slf4j//缓存切面记录
public class CacheAspect {
    //连接点：封装了目标方法和当前切面的信息 joinPoint
    //环绕通知：
    @Autowired
    CacheService cacheService;

    //拦截目标方法的执行
    @Autowired
    RedissonClient redissonClient;

    /*
     * 获取目标的带泛值的返回值类型
     *
     * */
    private static Type getMethodReturnType(ProceedingJoinPoint joinPoint) {
        // 从连接点获取方法签名
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        // 获取方法的带泛值的返回类型
        Type returnType = signature.getMethod().getGenericReturnType();
        return returnType;
    }

    //  @Pointcut("execution(public * com.cheng.gmall.item.service.SkuDetailService.getSkuDetailData(java.lang.Long,..) )")
    @Pointcut(value = "@annotation(com.cheng.gmall.item.aspect.MallCache)")
    public void pc() {
    }

    @Around(value = "pc()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        boolean tryLock = false;
        RLock rLock = null;
        try {
            log.info("缓存切面介入.....");
            //拿到目标方法的所有参数
//            Object[] args = joinPoint.getArgs();
//            Long skuid = (Long) args[0];
            Type returnType = getMethodReturnType(joinPoint);
            //得到注解
            MallCache mallCache = getMethodAnnotation(joinPoint, MallCache.class);
            // 计算表达式的值
            String cacheEval = "";
            if (StringUtils.isEmpty(mallCache.cacheKey())) {
                cacheEval = getCacheKeyDefault(mallCache.cacheKey(), joinPoint);
            } else {
                cacheEval = evalExpression(mallCache.cacheKey(), joinPoint, String.class);
            }


            //[动态:缓存中的数据类型]
            Object cache = cacheService.getCacheData(cacheEval, returnType);
            //缓存中有  直接返回
            if (cache != null) {
                log.info("缓存命中");
                return cache;
            }
            //3.缓存没有，先问位图
            log.info("缓存未命中,先问位图");
            //bitmap要判定的值不一样
//            boolean contain = cacheService.mightContain(skuid);
            //key可以自己指定
            String bitMapName = mallCache.bitMapName();

            if (!StringUtils.isEmpty(bitMapName)) {
                //value可以计算
                Assert.hasLength(mallCache.bitMapKey(), "bitmap索引位置必须给定");
                Long bitMapIndex = evalExpression(mallCache.bitMapKey(), joinPoint, Long.class);


                boolean contain = cacheService.mightContain(bitMapName, bitMapIndex);
                if (!contain) {
                    //位图说没有
                    log.info("位图拦截了攻击请求....");
                    return null;
                }
            }//为空  位图不用,直接走下面的流程

            //位图说有，准备回源  先上锁
            String lockKey = "lock:";
            if (StringUtils.isEmpty(mallCache.lockKey())) {
                lockKey = lockKey + cacheEval;
            } else {
                lockKey = evalExpression(mallCache.lockKey(), joinPoint, String.class);
            }

            rLock = redissonClient.getLock(lockKey);
            tryLock = rLock.tryLock();
            if (tryLock) {
                //执行目标方法
                //极端情况下：
                //A：缓存？==位图？==得
                log.info("获取分布式锁成功,准备回源");

                //缓存双检查
                cache = cacheService.getCacheData(cacheEval, returnType);

                if (cache != null) {
                    return cache;
                }
                log.info("开始回源");
                Object proceed = joinPoint.proceed();
                log.info("回源成功,返回....");
                //保存到缓存中

                long ttl = mallCache.ttl();
                TimeUnit unit = mallCache.unit();
                cacheService.saveCacheData(cacheEval, proceed, ttl, unit);
                //返回
                return proceed;
            }
            //没抢到锁的等待一段时间直接查询缓存即可
            TimeUnit.MILLISECONDS.sleep(300);
            cache = cacheService.getCacheData(cacheEval, returnType);
            return cache;
        } finally {
            if (tryLock) {
                rLock.unlock();
            }
        }

    }

    private String getCacheKeyDefault(String cacheKey, ProceedingJoinPoint joinPoint) {

        // 没传表达式; 默认使用方法名全签名+参数列表
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        String methodName = signature.getMethod().getName();
        String className = signature.getMethod().getDeclaringClass().toString().replace("class", "");
        Object params = Arrays.stream(joinPoint.getArgs()).reduce((o1, o2) -> o1.toString() + "_" + o2.toString()).get();
        return className + ":" + methodName + ":" + params.toString();

    }

    /**
     * 评估表达式，并返回表达式的值
     *
     * @param expr      表达式字符串
     * @param joinPoint 切入点对象
     * @return 表达式的值
     */
    private <T> T evalExpression(String expr, ProceedingJoinPoint joinPoint, Class<T> reType) {


        //1.创建表达式解析器
        SpelExpressionParser parser = new SpelExpressionParser();
        //2.解析表达式
        Expression expression = parser.parseExpression(expr, ParserContext.TEMPLATE_EXPRESSION);
        //3.得到表达式值
        EvaluationContext ec = new StandardEvaluationContext();
        ec.setVariable("args", joinPoint.getArgs());
        T value = expression.getValue(ec, reType);

        return value;
    }

    /*
     * 获取方法上的指定注解
     * */
    private <T extends Annotation> T getMethodAnnotation(ProceedingJoinPoint joinPoint, Class<T> clz) {
        // 从连接点获取方法签名
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        // 获取方法对象
        Method method = signature.getMethod();
        // 获取指定类型的方法注解
        T annotation = method.getDeclaredAnnotation(clz);
        return annotation;
    }


//    // 在执行指定方法前触发前置通知
//    @Before(value = "pc()")
//    public void haaha() {
//    }
//    //后置通知
//    @After(value = "pc()")
//    public void after() {
//    }
//    //返回通知
//    @AfterReturning(value = "pc()")
//    public void returning() {
//    }
//    //异常通知
//    @AfterThrowing(value = "pc()", throwing = "e")
//    public void throwing(Throwable e) {
//    }
}
